/*
  Copyright (c) 2008 Romain Gaucher <r@rgaucher.info>

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0
                
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#ifndef SECTYPES_H
#define SECTYPES_H

#include <iostream>
#include <stdexcept>
#include <string>
#include <list>
#include <algorithm>

#include "intnumber.h"
#include "range.h"


template<int N1, int N2> 
struct MAX {
	enum {val = N1 > N2 ? N1 : N2};
};

template<int N1, int N2>
struct promote_size {
	enum { N_promote = MAX<N1,N2>::val };
};
template<bool U1, bool U2>
struct promote_type {
	enum { U_promote = U1 & U2}; // <-- lazy promotion.. need to fix that
};


/*
	Integer<32,unsigned=true|false>
	Integer<32>
*/
template<unsigned int N, bool U=false>
class Integer
{
  protected:
	unsigned int nbytes;
	bool           sign;
	IntNumber     value;
	Range<N,U>    type_range;
	Range<N,U>    eval_range;
	//! backtrack the values
	std::list<IntNumber> history;
  
  public:
	IntNumber wrap(const Integer& i) const {
		IntNumber ret;
		IntNumber R_MAX = type_range.max() + IntNumber(1);
		IntNumber R_MIN = type_range.min();
		if (!type_range.inRange(i.value)) {
			std::cout << "not in range [" << R_MIN << ',' << R_MAX - IntNumber(1) << "]" << std::endl;
			switch(sign) {
				case true:
					{ ret = i.value % (R_MAX - R_MIN) + R_MIN;  break; }
				default:
					{ ret = (i.value >= 0 ? (i.value % (R_MAX)) : (R_MAX + i.value)) ;  break; }				
			}
			std::cout << "overflow wrapping -> " << ret << " from "<< i.value << std::endl;
		}
		else
			ret = i.value;
		require(type_range.inRange(ret), "integer out of type range");
		return ret;
	}
	
  protected:
	Integer(const Integer& i, const IntNumber& n) 
	  : nbytes(i.nbytes), sign(i.sign), value(n),
	    type_range(i.type_range), eval_range(i.eval_range)
	{

	}	
	
  public:
	Integer()
	  : nbytes(N), sign(not U) 
	{ /* */	}
	
	Integer(unsigned int _nb,bool _unsigned)
	  : nbytes(_nb), sign(not _unsigned) 
	{ /* */	}	

	Integer(const Integer& i) 
	  : nbytes(i.nbytes), sign(i.sign), value(i.value),
	    type_range(i.type_range), eval_range(i.eval_range)
	{
		if (!i.history.empty())
			std::copy(i.history.begin(),i.history.end(),history.begin());
	}

	Integer& operator= (const Integer& i) {
		sign   = i.sign;
		history.push_back(value);
		value  = wrap(i);
		type_range = i.type_range;
		eval_range = i.eval_range;	
		return *this;	
	}

	Integer& operator= (const IntNumber& n) {
		Integer integer(*this,n);
		history.push_back(value);
		value  = wrap(integer);
		return *this;	
	}
	/*
	template<int N2, bool U2>
	Integer<promote_size<N,N2>::N_promote, promote_type<U,U2>::U_promote >& 
	operator= (const Integer<N2,U2>& i) {
		history.push_back(value);
		value  = wrap(i);
		return *this;	
	}*/
	
	// extract ranges
	Range<N,U> getTypeRange() const { return type_range; }
	Range<N,U> getEvalRange() const { return eval_range; }
	
  public:
	IntNumber getValue() const { return value; }
	void setValue(const IntNumber& n) { value = n; }
};

//

template<int N1, bool U1, int N2, bool U2>
Integer<promote_size<N1,N2>::N_promote, promote_type<U1,U2>::U_promote > 
operator+ (const Integer<N1,U1>& i, const Integer<N2,U2>& j) {
	Integer<promote_size<N1,N2>::N_promote,promote_type<U1,U2>::U_promote> k; 
	k.setValue(i.getValue() + j.getValue());
	return k;
}

template<int N1, bool U1, int N2, bool U2>
Integer<promote_size<N1,N2>::N_promote, promote_type<U1,U2>::U_promote > 
operator= (const Integer<N1,U1>& i, const Integer<N2,U2>& j) {
	Integer<promote_size<N1,N2>::N_promote,promote_type<U1,U2>::U_promote> k; 
	k.setValue(j.getValue());
	return k;
}


//

typedef Integer<32,false> Int32;
typedef Integer<16,false> Int16;
typedef Integer<32,true > UInt32;
typedef Integer<16,true > UInt16;



#endif

